package com.nineclock.attendance.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.nineclock.attendance.dto.AttendGroupDTO;
import com.nineclock.attendance.dto.AttendPunchDTO;
import com.nineclock.attendance.dto.AttendPunchUserWholeDayDTO;
import com.nineclock.attendance.dto.AttendsDTO;
import com.nineclock.attendance.enums.AttendEnums;
import com.nineclock.attendance.mapper.AttendPunchMapper;
import com.nineclock.attendance.pojo.AttendPunch;
import com.nineclock.attendance.service.AttendGroupService;
import com.nineclock.attendance.service.AttendPunchService;
import com.nineclock.attendance.utils.CustomHandler;
import com.nineclock.attendance.utils.PunchUtils;
import com.nineclock.common.enums.ResponseEnum;
import com.nineclock.common.exception.NcException;
import com.nineclock.common.utils.BeanHelper;
import com.nineclock.common.utils.CurrentUserHolder;
import com.nineclock.common.utils.DateTimeUtil;
import com.nineclock.system.dto.SysCompanyUserDTO;
import com.nineclock.system.feign.SysOrganizationFeign;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
public class AttendPunchServiceImpl implements AttendPunchService {

    @Autowired
    AttendGroupService attendGroupService;
    @Autowired
    AttendPunchMapper attendPunchMapper;
    @Autowired
    SysOrganizationFeign organizationFeign;

    @Override
    public void punch(AttendPunchDTO attendPunchDTO) {
        if(attendPunchDTO == null){
            throw new NcException(ResponseEnum.INVALID_PARAM_ERROR);
        }
        //1. 转换为考勤对象
        AttendPunch attendPunch = BeanHelper.copyProperties(attendPunchDTO, AttendPunch.class);

        //1.1设置打卡信息
        attendPunch.setCreateTime(new Date() );
        attendPunch.setCompanyId(CurrentUserHolder.get().getCompanyId());
        attendPunch.setCompanyUserId(CurrentUserHolder.get().getCompanyUserId());
        //1.2 设置打卡时间
        attendPunch.setPunchTime(new Date());
        attendPunch.setPunchDateStr(DateTimeUtil.dateToStr(new Date( ), DateTimeUtil.TIME_FORMAT_2));

        //设置上午打卡，还是下午打卡
        attendPunch.setPunchOnOffWork(DateTimeUtil.noonType(new Date()) + 1);

        //2. 获取考勤组对象
        AttendGroupDTO attendGroupDTO = attendGroupService.getAttendGroupByUserId();

        //3.1 判断是否有效打卡 - 是否在有效范围内
        boolean validatePunchArea = PunchUtils.validatePunchArea(attendPunchDTO, attendGroupDTO);
        attendPunch.setEffectiveValid(validatePunchArea);
        if(!validatePunchArea){
            throw new NcException(ResponseEnum.PUNCH_INVALID_AREA);
        }
        //判断是由为有效打卡-是否为工作日
        boolean validateWorkDay = PunchUtils.validateWorkDay(attendGroupDTO);
        if(!validatePunchArea){
            throw new NcException(ResponseEnum.PUNCH_INVALID_DAY);
        }
        // 判断是否为有效打卡
        boolean validatePunch = this.validatePunch(attendPunch);

        //如果打卡有效，设置打卡类型
        if(validatePunch){
            PunchUtils.setPunchType(attendPunch, attendGroupDTO);
        }
        //保存打卡信息
        attendPunchMapper.insert(attendPunch);
    }


    private boolean validatePunch(AttendPunch attendPunch) {

        LambdaQueryWrapper<AttendPunch> punchQw = new LambdaQueryWrapper<>();

        punchQw.eq(AttendPunch::getPunchOnOffWork, attendPunch.getPunchOnOffWork());
        punchQw.eq(AttendPunch::getCompanyId, CurrentUserHolder.get().getCompanyId());
        punchQw.eq(AttendPunch::getCompanyUserId, CurrentUserHolder.get().getCompanyUserId());
        punchQw.eq(AttendPunch::getPunchDateStr , attendPunch.getPunchDateStr());

        Integer count = attendPunchMapper.selectCount(punchQw);

        if (count==0){
            attendPunch.setEffectiveValid(true);
        }else {
            if (attendPunch.getPunchOnOffWork()==1){
                attendPunch.setEffectiveValid(false);
            }else {
                attendPunch.setEffectiveValid(true);

                AttendPunch updateAttendPunch = new AttendPunch();
//                TODO 待测试
                updateAttendPunch.setEffectiveValid(false);
                attendPunchMapper.update(updateAttendPunch, punchQw);
            }
        }
        return attendPunch.getEffectiveValid();
    }

    /**
     * 根据系统用户ID获取全天的打卡数据
     * @return
     */
    @Override
    public AttendPunchUserWholeDayDTO queryPunchRecord() {
        AttendPunchUserWholeDayDTO wholeDayDTO = new AttendPunchUserWholeDayDTO();

        //获取企业员工ID , 企业ID , 当前日期字符串
        Long companyUserId = CurrentUserHolder.get().getCompanyUserId();
        Long companyId = CurrentUserHolder.get().getCompanyId();

        String dateToStr = DateTimeUtil.dateToStr(new Date(), DateTimeUtil.TIME_FORMAT_2);
        //1. 查询上午有效打卡情况
        LambdaQueryWrapper<AttendPunch> amQueryWrapper = new LambdaQueryWrapper<AttendPunch>();
        amQueryWrapper.eq(AttendPunch::getCompanyId, companyId)
                .eq(AttendPunch::getCompanyUserId, companyUserId)
                .eq(AttendPunch::getEffectiveValid, true)
                .eq(AttendPunch::getPunchOnOffWork, AttendEnums.NOON_TYPE_MORNING.value())
                .eq(AttendPunch::getPunchDateStr, dateToStr);

        AttendPunch amPunch = attendPunchMapper.selectOne(amQueryWrapper);

        //2. 查询下午有效打卡情况
        LambdaQueryWrapper<AttendPunch> pmQueryWrapper = new LambdaQueryWrapper<AttendPunch>();
        pmQueryWrapper.eq(AttendPunch::getCompanyId, companyId)
                .eq(AttendPunch::getCompanyUserId, companyUserId)
                .eq(AttendPunch::getEffectiveValid, true)
                .eq(AttendPunch::getPunchOnOffWork, AttendEnums.NOON_TYPE_AFTERNOON.value())
                .eq(AttendPunch::getPunchDateStr, dateToStr);
        List<AttendPunch> attendPunches = attendPunchMapper.selectList(pmQueryWrapper);

        AttendPunch pmPunch = attendPunchMapper.selectOne(pmQueryWrapper);
        //  组装数据
        wholeDayDTO.setAttendPunchMorningResponse(BeanHelper.copyProperties(amPunch, AttendPunchDTO.class));
        wholeDayDTO.setAttendPunchAfterNoonResponse(BeanHelper.copyProperties(pmPunch, AttendPunchDTO.class));

        return wholeDayDTO;
    }

    @Override
    public List<AttendPunchDTO> queryMembers(String startTime, String endTime) {
//        获取时间段内的日期
        List<String> dateList = this.getSpanDateList(startTime, endTime);
//      查询企业下所有用户数据
        List<SysCompanyUserDTO> companyUserDTOS = organizationFeign.queryAllCompanyUser().getData();
//     查询每个员工每天的打卡情况
        List<AttendPunchDTO> attendPunchDTOS = this.getAttendPunchDTOS(startTime, endTime, dateList, companyUserDTOS);

        return attendPunchDTOS;
    }

    private List<AttendPunchDTO> getAttendPunchDTOS(String startTime, String endTime, List<String> spanDateList, List<SysCompanyUserDTO> companyUserDTOs) {
        return companyUserDTOs.stream().map(sysCompanyUserDTO -> {
            //封装用户信息
            AttendPunchDTO attendPunchDTO = BeanHelper.copyProperties(sysCompanyUserDTO, AttendPunchDTO.class);

            //获取该用户的指定时间范围内的有效打卡数据
            LambdaQueryWrapper<AttendPunch> attendPunchQueryWrapper = new LambdaQueryWrapper<>();
            attendPunchQueryWrapper.eq(AttendPunch::getCompanyUserId, sysCompanyUserDTO.getId());
            attendPunchQueryWrapper.eq(AttendPunch::getEffectiveValid, true);
            attendPunchQueryWrapper.between(AttendPunch::getPunchDateStr, startTime, endTime);

            //执行查询考勤数据操作, 查询出来的数据 , 每一天会包含两条数据(上午打卡, 下午打卡)
            List<AttendPunch> attendPunches = attendPunchMapper.selectList(attendPunchQueryWrapper);

            //对获取到的打卡数据进行处理
            List<AttendsDTO> attendsDTOS = this.handleAttendPunchEveryDay(spanDateList, attendPunches);

            attendPunchDTO.setAttendsList(attendsDTOS);

            return attendPunchDTO;
        }).collect(Collectors.toList());
    }

    private List<AttendsDTO> handleAttendPunchEveryDay(List<String> spanDateList , List<AttendPunch> attendPunches) {
        //将获取到的打卡信息列表进行分组处理
        Map<String, List<AttendPunch>> punchMap = attendPunches.stream().collect(Collectors.groupingBy(AttendPunch::getPunchDateStr));

        //获取每一天的打卡情况
        List<AttendsDTO> attendsDTOS = spanDateList.stream().map(dateStr -> {
            AttendsDTO attendsDTO = new AttendsDTO();
            attendsDTO.setAttendDate(dateStr);

            List<AttendPunch> punches = punchMap.get(dateStr);//获取到某一天的打卡数据
            //如果为空, 则说明上下午都没有打卡
            if (CollectionUtil.isEmpty(punches)) {
                attendsDTO.setAttendStatus("缺卡/缺卡");
            } else {
                attendsDTO.setAttendStatus(this.handleAttendStatus(punches));
            }
            return attendsDTO;
        }).collect(Collectors.toList());

        return attendsDTOS;
    }


    private String handleAttendStatus(List<AttendPunch> punches) {
        String amAttendStatus = "缺卡";//上午 - 考勤
        String pmAttendStatus = "缺卡";//下午 - 考勤
        if(CollectionUtil.isNotEmpty(punches)){
            for (AttendPunch punch : punches) {
                if(punch.getPunchOnOffWork() == AttendEnums.NOON_TYPE_MORNING.value()){
                    amAttendStatus = this.getPushDesc(punch.getMorningPunchType());
                }else if (punch.getPunchOnOffWork() == AttendEnums.NOON_TYPE_AFTERNOON.value()){
                    pmAttendStatus = this.getPushDesc(punch.getAfternoonPunchType());
                }
            }
        }
        return amAttendStatus+"/"+pmAttendStatus;
    }


    private String getPushDesc(Integer morningPunchType) {
        String desc = "";
        switch (morningPunchType){
            case 1:
                desc = AttendEnums.PUNCH_TYPE_OK.toString();
                break;
            case 2:
                desc = AttendEnums.PUNCH_TYPE_LATE.toString();
                break;
            case 3:
                desc = AttendEnums.PUNCH_TYPE_EARLY.toString();
                break;
            case 4:
                desc = AttendEnums.PUNCH_TYPE_STAYALAY.toString();
                break;
            case 5:
                desc = AttendEnums.PUNCH_TYPE_NOT_WORK_DAY.toString();
                break;
            default:
                break;
        }
        return desc;
    }


    private List<String> getSpanDateList(String startTime, String endTime) {
        List<Date> dateList = DateTimeUtil.getDatesBetweenTwoDate(startTime, endTime, DateTimeUtil.TIME_FORMAT_2);
        List<String> dateStrList = dateList.stream().map(date -> DateTimeUtil.dateToStr(date, DateTimeUtil.TIME_FORMAT_2)).collect(Collectors.toList());
        return dateStrList;
    }

    @Override
    public void exportAttendData(String startTime, String endTime) throws IOException {
//        获取时间端的打卡信息
        List<AttendPunchDTO> attendPunchDTOS = this.queryMembers(startTime, endTime);
//      设置下载文件的头信息
        HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
        response.setHeader("Content-disposition", "attachment;filename=Attendance.xlsx");
        response.setContentType("application/octet-stream;charset=UTF-8");
        response.setCharacterEncoding("utf-8");

        //写入Excel
        EasyExcel.write(response.getOutputStream())
                .excelType(ExcelTypeEnum.XLS)
                .head(handleHeader(attendPunchDTOS))
                .registerWriteHandler(new CustomHandler()) //设置列宽
                .registerWriteHandler(horizontalCellStyleStrategy()) //设置水平居中
                .sheet("考勤记录")
                .doWrite(handleData(attendPunchDTOS));
    }

    //设置样式
    public HorizontalCellStyleStrategy horizontalCellStyleStrategy(){

        WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
        //内容策略 - 水平居中
        contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);

        //设置字体
        WriteFont font = new WriteFont();
        font.setFontName("阿里巴巴普惠体");
        font.setFontHeightInPoints((short)11);
        contentWriteCellStyle.setWriteFont(font);

        HorizontalCellStyleStrategy horizontalCellStyleStrategy = new HorizontalCellStyleStrategy(contentWriteCellStyle, contentWriteCellStyle);

        return horizontalCellStyleStrategy;
    }

    //组装表头信息
    private List<List<String>> handleHeader(List<AttendPunchDTO> punchDTOS){
        List<List<String>> headers = new ArrayList<>();

        headers.add(Arrays.asList("姓名"));
        headers.add(Arrays.asList("工号"));
        headers.add(Arrays.asList("部门"));
        headers.add(Arrays.asList("职位"));

        //动态表头
        AttendPunchDTO attendPunchDTO = punchDTOS.get(0);
        if(attendPunchDTO != null){
            List<AttendsDTO> attendsList = attendPunchDTO.getAttendsList();
            if(CollectionUtil.isNotEmpty(attendsList)){
                for (AttendsDTO attendsDTO : attendsList) {
                    headers.add(Arrays.asList(attendsDTO.getAttendDate()));
                }
            }
        }
        return headers;
    }

    //组装数据
    private List<List<String>> handleData(List<AttendPunchDTO> punchDTOS){
        List<List<String>> dataList = new ArrayList<List<String>>();

        if(CollectionUtil.isNotEmpty(punchDTOS)){
            for (AttendPunchDTO punchDTO : punchDTOS) {
                //姓名, 工号, 部门, 职位
                List<String> data = new ArrayList<String>();
                data.add(punchDTO.getUserName());
                data.add(punchDTO.getWorkNumber());
                data.add(punchDTO.getDepartmentName());
                data.add(punchDTO.getPost());

                //动态信息 : 每日打卡情况
                List<AttendsDTO> attendsList = punchDTO.getAttendsList();
                if(CollectionUtil.isNotEmpty(attendsList)){
                    for (AttendsDTO dto : attendsList) {
                        data.add(dto.getAttendStatus());
                    }
                }
                dataList.add(data);
            }
        }

        return dataList;
    }
}
